"use strict";

var $ = require("jquery"),
    executeAsyncMock = require("../../helpers/executeAsyncMock.js"),
    DataSource = require("data/data_source/data_source").DataSource,
    Store = require("data/abstract_store"),
    ArrayStore = require("data/array_store"),
    ODataStore = require("data/odata/store"),
    AggregateCalculator = require("ui/data_grid/aggregate_calculator");

require("../../../node_modules/jquery-mockjax/dist/jquery.mockjax.js");

var TEN_NUMBERS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var moduleConfig = {
    beforeEach: function() {
        executeAsyncMock.setup();
    },
    afterEach: function() {
        executeAsyncMock.teardown();
    }
};


QUnit.module("loadSingle");
QUnit.test("loadSingle call doesn't affect to state", function(assert) {
    var source = new DataSource(TEN_NUMBERS),
        changedFired = false,
        loadingChangedFired = false;

    source.on("changed", function() { changedFired = true; });
    source.on("loadingChanged", function() { loadingChangedFired = true; });

    source.loadSingle("this", 2).done(function(data) {
        assert.ok(!changedFired);
        assert.ok(!loadingChangedFired);
        assert.deepEqual(source.items(), []);
    });
});

QUnit.test("lookup with key specified", function(assert) {
    new DataSource({
        store: {
            type: "array",
            data: [
                { a: "1", i: 1 },
                { a: "2", i: 2 }
            ],
            key: "a"
        },
        filter: ["a", "<>", 1]
    }).loadSingle("a", "1").done(function(r) {
        assert.deepEqual(r, { a: "1", i: 1 });
    });
});

QUnit.test("with no key specified", function(assert) {
    assert.expect(3);

    var source = new DataSource({
        store: [
            { a: "1", i: 1 },
            { a: "2", i: 2 },
            { a: "2", i: 3 }
        ],
        filter: ["a", "<>", 1],
        sort: { field: "i", desc: true }
    });

    source.pageIndex(1);

    $.when(
        source.loadSingle("a", "2").done(function(r) {
            assert.ok(r, "should ignore skip option");
            assert.deepEqual(r, { a: "2", i: 3 }, "shouldn't ignore sort option");
        }),

        source.loadSingle("a", "1").fail(function() {
            assert.ok(true, "shouldn't retrieve filtered values");
        })
    );
});

QUnit.test("use key if no propName specified", function(assert) {
    new DataSource({
        store: {
            type: "array",
            data: [{ a: 1 }],
            key: "a"
        }
    }).loadSingle(1).done(function(r) {
        assert.deepEqual(r, { a: 1 }, "should use store.key() as propName");
    });
});

QUnit.test("doesn't apply postProcess", function(assert) {
    new DataSource({
        store: [1],
        postProcess: function(data) {
            return [null].concat(data);
        }
    }).loadSingle("this", 1).done(function(item) {
        assert.equal(item, 1);
    });
});

QUnit.test("error handling", function(assert) {
    assert.expect(0);

    var source = new DataSource(TEN_NUMBERS);

    var failFired = $.Deferred(),
        loadErrorFired = $.Deferred();

    source.on("loadError", function() {
        loadErrorFired.resolve();
    });

    source.loadSingle(function(obj) { throw Error("forced"); }, 1)
        .fail(function() { failFired.resolve(); });

    $.when(loadErrorFired, failFired);
});

QUnit.test("error handling (with key)", function(assert) {
    assert.expect(0);

    var source = new DataSource({
        store: new (ArrayStore.inherit({
            _byKeyImpl: function() { return $.Deferred().reject(Error("forced")).promise(); }
        }))({ key: "a", data: [] })
    });

    var failFired = $.Deferred(),
        loadErrorFired = $.Deferred();

    source.on("loadError", function() { loadErrorFired.resolve(); });
    source.loadSingle("a", 1).fail(function() { failFired.resolve(); });

    $.when(loadErrorFired, failFired);
});

QUnit.test("error handling (data item cannot be found, T283407)", function(assert) {
    assert.expect(3);

    var handleError = function(error) {
        assert.ok(error instanceof Error);
    };

    new DataSource({
        store: new ArrayStore({
            data: [{ d: 1, b: 1 }],
            key: "d"
        }),
        onLoadError: handleError
    })
        .on("loadError", handleError)
        .loadSingle("b", 2) // forces data mining through load implementation
        .fail(handleError)
        .done(function() {
            assert.ok(false);
        });
});

QUnit.test("don't force byKey for CustomStore(loadMode=raw, byKey=undefined)", function(assert) {
    var done = assert.async();

    var source = new DataSource({
        load: function() {
            return [ { a: 1 }, { a: 2 } ];
        },
        loadMode: "raw"
    });

    source.loadSingle("a", 2).done(function(item) {
        assert.equal(item.a, 2);
        done();
    });
});

QUnit.module("simple tests");
QUnit.test("initial state and load", function(assert) {
    var source = new DataSource({
        store: TEN_NUMBERS,
        pageSize: 3
    });

    assert.ok(!source.isLoaded());
    assert.deepEqual(source.items(), []);
    assert.equal(source.pageIndex(), 0);

    source.load().done(function(data, extra) {
        assert.ok(source.isLoaded());
        assert.deepEqual(source.items(), data);
        assert.deepEqual(data, [1, 2, 3]);
        assert.equal(source.totalCount(), -1);
    });
});

QUnit.test("requireTotalCount", function(assert) {
    var loadingChangedCount,
        countCallCount,
        enumerateCallCount;

    var createWrappedQuery = function(origQuery) {

        function dummy() {
            return this;
        }

        return {
            slice: dummy,
            count: function() {
                countCallCount++;
                return origQuery.count();
            },
            enumerate: function() {
                enumerateCallCount++;
                return origQuery.enumerate();
            }
        };
    };

    var MyStore = ArrayStore.inherit({
        createQuery: function() {
            return createWrappedQuery(this.callBase.apply(this, arguments));
        }
    });

    var source = new DataSource({
        store: new MyStore(TEN_NUMBERS),
        requireTotalCount: true
    });

    source.on("loadingChanged", function() {
        loadingChangedCount++;
    });

    loadingChangedCount = 0;
    countCallCount = 0;
    enumerateCallCount = 0;

    source.load().done(function(r, extra) {
        assert.equal(extra.totalCount, 10);
        assert.equal(source.totalCount(), 10);

        assert.equal(countCallCount, 1);
        assert.equal(enumerateCallCount, 1);
        assert.equal(loadingChangedCount, 2);
    });

});

QUnit.test("load options passed to store", function(assert) {
    new DataSource({
        store: TEN_NUMBERS,
        filter: ["this", "<", 4],
        sort: { getter: "this", desc: true }
    })
        .load()
        .done(function(data) {
            assert.deepEqual(data, [3, 2, 1]);
        });
});

QUnit.test("map function", function(assert) {
    new DataSource({
        store: [14, 6],
        map: function(item, index) { return index + ":" + item; }
    })
        .load()
        .done(function(data) {
            assert.deepEqual(data, ["0:14", "1:6"]);
        });
});

QUnit.test("map function + grouping", function(assert) {
    new DataSource({
        store: [{ a: 1, b: 3 }],
        group: ["a", "b"],
        map: function(item) { return item.a * item.b; }
    })
        .load()
        .done(function(data) {
            assert.deepEqual(data, [{
                key: 1,
                items: [{
                    key: 3,
                    items: [3]
                }]
            }]);
        });
});

QUnit.test("map function + grouping with null items", function(assert) {
    new DataSource({
        store: [{ a: 1, b: 3 }],
        group: ["a", "b"],
        map: function(item) { return item.a * item.b; },
        onCustomizeLoadResult: function(loadResult) {
            loadResult.data[0].items[0].items = null;
        }
    })
        .load()
        .done(function(data) {
            assert.deepEqual(data, [{
                key: 1,
                items: [{
                    key: 3,
                    items: null
                }]
            }]);
        });
});

QUnit.test("map function + grouping + aggregating", function(assert) {
    new DataSource({
        store: [{ a: 1, b: 3 }],
        group: ["a", "b"],
        map: function(item) { return item.a * item.b; },
        onCustomizeLoadResult: function(loadResult) {
            var calculator = new AggregateCalculator({
                data: loadResult.data,
                groupAggregates: [
                    { aggregator: "count" }
                ],
                groupLevel: 2
            });

            calculator.calculate();
        }
    })
        .load()
        .done(function(data) {
            assert.deepEqual(data, [{
                key: 1,
                aggregates: [1],
                items: [{
                    key: 3,
                    aggregates: [1],
                    items: [3]
                }]
            }]);
        });
});

QUnit.test("postProcess function", function(assert) {
    new DataSource({
        store: [1],
        postProcess: function(list) {
            return list.concat(["extra"]);
        }
    })
        .load()
        .done(function(data) {
            assert.deepEqual(data, [1, "extra"]);
        });
});

QUnit.test("page index change", function(assert) {
    var source = new DataSource({
        store: TEN_NUMBERS,
        pageIndex: 1,
        pageSize: 3
    });

    source.load().done(function() {
        assert.equal(source.pageIndex(), 1);
        assert.deepEqual(source.items(), [4, 5, 6]);

        source.on("changed", function() {
            assert.equal(source.pageIndex(), 2);
            assert.deepEqual(source.items(), [7, 8, 9]);
        });

        source.pageIndex(2);
        source.load();
    });

});

QUnit.test("paginate option", function(assert) {
    var data = [];

    for(var i = 0; i < 100; i++) {
        data.push(i);
    }

    var store = new ArrayStore(data),
        source = new DataSource({
            store: store,
            paginate: false
        });

    assert.ok(source.isLastPage());
    source.load().done(function() {
        assert.equal(source.items().length, 100);
    });
});

QUnit.test("paginate method", function(assert) {
    var source = new DataSource({
        store: TEN_NUMBERS,
        pageSize: 3
    });

    source.pageIndex(1);

    assert.ok(source.paginate());
    assert.ok(!source.isLastPage());

    source.paginate(true);

    assert.ok(!source.isLastPage());
    assert.equal(source.pageIndex(), 1);

    source.paginate(false);

    assert.ok(source.isLastPage());
    assert.equal(source.pageIndex(), 0);

    source.paginate(true);
    assert.ok(!source.isLastPage());
});

QUnit.test("reload", function(assert) {
    var fn;

    var source = new DataSource({
        load: function(options) {
            fn(options);
            return TEN_NUMBERS;
        }
    });

    fn = function(options) {
        options.userData.foo = "bar";
    };

    source.load();

    assert.ok(source.isLoaded());
    assert.ok(source.isLastPage());

    fn = function(options) {
        assert.ok(!source.isLoaded());
        assert.ok(!source.isLastPage());
        assert.equal(source.totalCount(), -1);
        assert.equal(source.items().length, 0);
        assert.ok($.isEmptyObject(options.userData));
    };

    source.reload();
});

QUnit.test("when grouped then not paginating by default", function(assert) {
    var data = [];

    for(var i = 0; i < 100; i++) {
        data.push({ i: i });
    }

    var store = new ArrayStore(data),
        source = new DataSource({
            store: store,
            group: "i"
        });

    source.load().done(function() {
        assert.equal(source.items().length, 100);
    });
});

QUnit.test("load failure", function(assert) {
    var source = new DataSource({
        store: [1, 2, 3],
        select: function() {
            throw Error("forced");
        }
    });

    var failFired = $.Deferred(),
        callbackFired = $.Deferred();

    source.on("loadError", function(error) {
        assert.equal(error.message, "forced");
        assert.ok(!source.isLoading());

        callbackFired.resolve();
    });

    source.load().fail(function(error) {
        assert.equal(error.message, "forced");
        failFired.resolve();
    });

    $.when(callbackFired, failFired);
});

QUnit.test("load in disposed state (B230839, B230785)", function(assert) {
    assert.expect(0);

    var source = new DataSource();
    source.dispose();

    source.load();
});

QUnit.test("store load callback fired in disposed state", function(assert) {
    assert.expect(0);

    var loaded = $.Deferred();

    var storeClass = Store.inherit({
        _loadImpl: function() {
            return loaded.promise();
        }
    });

    var store = new storeClass();

    var source = new DataSource(store);

    source.load();
    source.dispose();
    loaded.resolve([1, 2, 3]);
});

QUnit.test("store returned not an array", function(assert) {
    var storeClass = Store.inherit({
        _loadImpl: function() {
            return $.Deferred().resolve(1);
        }
    });

    var store = new storeClass();

    new DataSource(store).load().done(function(r) {
        assert.deepEqual(r, [1]);
    });
});

QUnit.test("dataSource knows key of its store, and knows store as well", function(assert) {
    var store = new ArrayStore({
        key: "id"
    });
    var source = new DataSource(store);
    assert.equal(source.key(), "id");
    assert.equal(source.store(), store);
});

QUnit.test("isLoading and loadingChanged", function(assert) {
    var MyStore = Store.inherit({
        load: function() {
            return this.testDeferred.promise();
        }
    });

    var store = new MyStore(),
        ds = new DataSource(store),
        d1 = $.Deferred(),
        d2 = $.Deferred(),
        changeCount = 0;

    ds.on("loadingChanged", function() {
        changeCount++;
    });

    assert.ok(!ds.isLoading());

    store.testDeferred = d1;
    ds.load().always(function() {
        assert.equal(changeCount, 1);
        assert.ok(ds.isLoading()); // is still loading

        d2.reject();
    });

    store.testDeferred = d2;
    ds.load().always(function() {
        assert.equal(changeCount, 2);
        assert.ok(!ds.isLoading());
    });

    assert.equal(changeCount, 1);
    assert.ok(ds.isLoading());

    d1.resolve([]);
});

QUnit.test("isLoading is false inside when changed fires", function(assert) {
    var source = new DataSource(TEN_NUMBERS);

    source.on("changed", function() {
        assert.ok(!source.isLoading());
    });

    source.load();
});

QUnit.test("customizeStoreLoadOptions", function(assert) {
    var source = new DataSource(TEN_NUMBERS);

    source.on("customizeStoreLoadOptions", function(options) {
        options.storeLoadOptions.filter = ["this", "=", 1];
    });

    source.load().done(function(data) {
        assert.deepEqual(data, [1]);
        assert.ok(!source.loadOptions().filter);
    });
});

QUnit.test("customizeStoreLoadOptions cache", function(assert) {
    var source = new DataSource(TEN_NUMBERS),
        loadingCount = 0,
        changedCount = 0;

    source.store().on("loading", function() {
        loadingCount++;
    });
    source.on("changed", function(options) {
        changedCount++;
    });

    source.load().done(function(data) {
        assert.deepEqual(data, TEN_NUMBERS);
        assert.deepEqual(source.items(), TEN_NUMBERS);
        assert.equal(loadingCount, 1);
        assert.equal(changedCount, 1);
    });

    source.on("customizeStoreLoadOptions", function(options) {
        options.data = [1, 2];
    });

    source.load().done(function(data) {
        assert.deepEqual(data, [1, 2]);
        assert.deepEqual(source.items(), [1, 2]);
        assert.equal(loadingCount, 1, "loading is not raised");
        assert.equal(changedCount, 2, "changed is raised");
    });
});

QUnit.test("customizeLoadResult", function(assert) {
    var source = new DataSource({
        map: function(i) {
            return String(i);
        },
        postProcess: function(data) {
            return $.extend(data, { extended: true });
        },
        store: [42]
    });

    source.on("customizeLoadResult", function(args) {
        assert.ok(!("foo" in args.data));
        assert.equal(args.data[0], 42);

        args.data = [24];
    });

    source.load().done(function(data, extra) {
        assert.equal(data[0], "24");
        assert.equal(data.extended, true);
    });
});

QUnit.test("cancel works", function(assert) {
    assert.expect(4);

    var source = new DataSource({
        load: function() {
            return $.Deferred().promise();
        }
    });

    var promise = source.load()
        .fail(handleFail)
        .done(handleDone);

    function handleFail() {
        assert.ok(true, "Should reach this point");
    }

    function handleDone() {
        assert.ok(false, "Shouldn't reach this point");
    }

    assert.ok(source.isLoading());

    source.cancel(promise.operationId);

    assert.ok(!source.isLoaded());
    assert.ok(!source.isLoading());
});

QUnit.test("canceling on customizeStoreLoadOptions", function(assert) {
    var source = new DataSource({
        load: mustNotReach
    });

    function mustNotReach() {
        assert.ok(false, "Shouldn't reach this point");
    }

    function handleFail() {
        assert.ok(true, "Should reach this point");
    }

    function handleDone() {
        mustNotReach();
    }

    source.on("customizeStoreLoadOptions", function(operation) {
        source.cancel(operation.operationId);
    });

    source.load()
        .fail(handleFail)
        .done(handleDone);
});

QUnit.test("cancel event flow", function(assert) {
    var loadingChangedLog = [];
    var ds = new DataSource({
        load: function() {
            return $.Deferred().promise();
        }
    });

    function mustNotReach() {
        assert.ok(false, "Shouldn't reach this point");
    }

    ds.on("changed", function() {
        mustNotReach();
    });

    ds.on("loadError", function() {
        mustNotReach();
    });

    ds.on("loadingChanged", function(state) {
        loadingChangedLog.push(state);
    });

    var promise = ds.load()
        .always(function(state) {
            assert.equal(state, "canceled");
        });

    ds.cancel(promise.operationId);

    assert.deepEqual(loadingChangedLog, [true, false]);
});

QUnit.test("search API, default impl, no selector", function(assert) {
    var store = new ArrayStore(["a", "b"]);

    var source = new DataSource(store);
    source.searchValue("a");

    source.load().done(function(r) {
        assert.equal(r.length, 1);
    });
});

QUnit.test("search API, default impl, single selector", function(assert) {
    var store = new ArrayStore([
            { text: "abc" },
            { text: "xyz" }
    ]);

    var source = new DataSource(store);
    source.searchExpr("text");
    source.searchValue("a");

    source.load().done(function(r) {
        assert.equal(r.length, 1);
        assert.equal(r[0].text, "abc");
    });
});

QUnit.test("search API, default impl, multi selectors, custom op", function(assert) {
    var store = new ArrayStore([
            { text: "abc", description: "xyz" },
            { text: "xyz", description: "abc" },
            { text: "xyz", description: "xyz" }
    ]);

    var source = new DataSource(store);
    source.searchExpr("text", "description");
    source.searchOperation("=");
    source.searchValue("abc");

    source.load().done(function(r) {
        assert.equal(r.length, 2);
    });
});

QUnit.test("search API, default impl, complex selector", function(assert) {
    var store = new ArrayStore([
            { date: new Date(1984, 5, 14) }
    ]);

    var source = new DataSource(store);
    source.searchExpr(function(item) { return item.date.getFullYear(); });
    source.searchOperation("=");
    source.searchValue(1984);

    source.load().done(function(r) {
        assert.equal(r.length, 1);
    });
});

QUnit.test("search API, custom impl", function(assert) {
    var MyStore = Store.inherit({

        ctor: function(o) {
            this.callBase(o);
            this._useDefaultSearch = false;
        },

        _loadImpl: function(options) {
            assert.ok(!options.filter);

            assert.equal(options.searchExpr, "expr");
            assert.equal(options.searchValue, "test text");
            assert.equal(options.searchOperation, "operation");

            return $.Deferred(); // api requires
        }

    });

    var source = new DataSource(new MyStore());
    source.searchOperation("operation");
    source.searchValue("test text");
    source.searchExpr("expr");

    source.load();
});

QUnit.test("search API, configuration from ctor", function(assert) {
    var source = new DataSource({
        searchExpr: "abc",
        searchOperation: "xyz",
        searchValue: 0,
        load: $.noop
    });

    assert.equal(source.searchExpr(), "abc");
    assert.equal(source.searchOperation(), "xyz");
    assert.strictEqual(source.searchValue(), 0);
});

QUnit.test("search API, useDefaultSearch", function(assert) {
    assert.expect(8);

    var sourceWithCustomSearch = new DataSource({
        useDefaultSearch: false,

        load: function(options) {
            assert.ok(!options.filter);

            assert.equal(options.searchExpr, "expr");
            assert.equal(options.searchValue, "test text");
            assert.equal(options.searchOperation, "operation");

            return $.Deferred().promise();
        }
    });

    var sourceWithDefaultSearch = new DataSource({
        useDefaultSearch: true,

        load: function(options) {
            assert.deepEqual(options.filter, [["expr", "operation", "test text"]]);

            assert.ok(!options.searchExpr);
            assert.ok(!options.searchValue);
            assert.ok(!options.searchOperation);

            return $.Deferred().promise();
        }
    });

    sourceWithCustomSearch.searchExpr("expr");
    sourceWithCustomSearch.searchValue("test text");
    sourceWithCustomSearch.searchOperation("operation");

    sourceWithDefaultSearch.searchExpr("expr");
    sourceWithDefaultSearch.searchValue("test text");
    sourceWithDefaultSearch.searchOperation("operation");

    $.when(
        sourceWithCustomSearch.load(),
        sourceWithDefaultSearch.load()
    ).fail(function() {
        assert.ok(false, "Shouldn't reach this point");
    });
});

QUnit.test("events API, changed", function(assert) {
    var cl = {};

    var source = new DataSource({
        store: [1, 2, 3],
        onChanged: function() {
            cl.onChanged_option = true;
        }
    });

    source.on("changed", function() {
        cl.on_changed_callback = true;
    });


    source.load().done(function(r) {
        assert.deepEqual(r, [1, 2, 3]);
        assert.deepEqual(cl, {
            onChanged_option: true,
            on_changed_callback: true
        });
    });
});

QUnit.test("events API, loadingChanged", function(assert) {
    var cl = {
        onLoadingChanged_option: [],
        on_loadingChanged_callback: []
    };
    var source = new DataSource({
        store: [1, 2, 3],
        onLoadingChanged: function(isLoading) {
            cl.onLoadingChanged_option.push(isLoading);
        }
    });

    source.on("loadingChanged", function(isLoading) {
        cl.on_loadingChanged_callback.push(isLoading);
    });


    source.load().done(function(r) {
        assert.deepEqual(r, [1, 2, 3]);
        assert.deepEqual(cl, {
            onLoadingChanged_option: [true, false],
            on_loadingChanged_callback: [true, false]
        });
    });
});

QUnit.test("events API, loadError", function(assert) {
    var cl = {};

    var source = new DataSource({
        store: [1, 2, 3],
        select: function() {
            throw new Error("Forced");
        },
        onLoadError: function(e) {
            cl.onLoadError_option = e.message;
        }
    });

    source.on("loadError", function(e) {
        cl.on_loadError_callback = e.message;
    });


    source.load().fail(function(e) {
        assert.equal(e.message, "Forced");
        assert.deepEqual(cl, {
            onLoadError_option: "Forced",
            on_loadError_callback: "Forced"
        });
    });
});

QUnit.test("events API, customizeLoadResult", function(assert) {
    var cl = {};

    var source = new DataSource({
        store: [1, 2, 3],
        onCustomizeLoadResult: function(loadResult) {
            cl.onCustomizeLoadResult_option = loadResult;

            delete cl.onCustomizeLoadResult_option.operationId;
            loadResult.foo = 1;
        }
    });

    source.on("customizeLoadResult", function(loadResult) {
        cl.on_customizeLoadResult_callback = loadResult;

        delete cl.on_customizeLoadResult_callback.operationId;
        loadResult.bar = 2;
    });


    source.load().done(function(r) {
        var expected = {
            data: [1, 2, 3],
            extra: undefined,
            foo: 1,
            bar: 2,

            storeLoadOptions: {
                skip: 0,
                take: 20,
                userData: {}
            }
        };

        assert.deepEqual(r, [1, 2, 3]);
        assert.deepEqual(cl, {
            onCustomizeLoadResult_option: expected,
            on_customizeLoadResult_callback: expected
        });
    });
});

QUnit.test("events API, customizeStoreLoadOptions", function(assert) {
    var cl = {},
        source = new DataSource({
            store: [1, 2, 3],
            onCustomizeStoreLoadOptions: function(loadOptions) {
                cl.onCustomizeStoreLoadOptions_option = loadOptions;
                delete cl.onCustomizeStoreLoadOptions_option.operationId;
            }
        });

    source.on("customizeStoreLoadOptions", function(loadOptions) {
        cl.on_customizeStoreLoadOptions_callback = loadOptions;
        delete cl.onCustomizeStoreLoadOptions_option.operationId;
    });


    source.load().done(function(r) {
        var expected = {
            storeLoadOptions: {
                skip: 0,
                take: 20,
                userData: {}
            }
        };

        assert.deepEqual(r, [1, 2, 3]);
        assert.deepEqual(cl, {
            onCustomizeStoreLoadOptions_option: expected,
            on_customizeStoreLoadOptions_callback: expected
        });
    });
});

QUnit.test("dispose unsubscribes events", function(assert) {
    var func = function() { };
    var source = new DataSource({
        store: [1, 2, 3],

        onChanged: func,
        onLoadError: func,
        onLoadingChanged: func,
        onCustomizeLoadResult: func,
        onCustomizeStoreLoadOptions: func
    });

    source.on("changed", func);
    source.on("loadError", func);
    source.on("loadingChanged", func);
    source.on("customizeLoadResult", func);
    source.on("customizeStoreLoadOptions", func);

    var events = source._eventsStrategy._events;
    assert.ok(events["changed"].has(func));
    assert.ok(events["loadError"].has(func));
    assert.ok(events["loadingChanged"].has(func));
    assert.ok(events["customizeLoadResult"].has(func));
    assert.ok(events["customizeStoreLoadOptions"].has(func));

    source.dispose();

    events = source._eventsStrategy._events;
    assert.ok(!events["changed"].has());
    assert.ok(!events["loadError"].has());
    assert.ok(!events["loadingChanged"].has());
    assert.ok(!events["customizeLoadResult"].has());
    assert.ok(!events["customizeStoreLoadOptions"].has());
});

QUnit.module("Changing store load options", moduleConfig);

QUnit.test("sort", function(assert) {
    var data = [
        { id: 1 },
        { id: 2 },
        { id: 3 }
    ];

    var source = new DataSource({
        store: data,
        map: function(i) { return i.id; }
    });

    source.sort("a");
    assert.equal(source.sort(), "a");

    source.sort("a", "b");
    assert.deepEqual(source.sort(), ["a", "b"]);

    source.sort({ field: "id", desc: true });
    assert.deepEqual(source.sort(), { field: "id", desc: true });

    source.load().done(function(r) {
        assert.deepEqual(r, [3, 2, 1]);
    });
});

QUnit.test("filter", function(assert) {
    var data = [
        { id: 1 },
        { id: 2 }
    ];

    var source = new DataSource({
        store: data,
        map: function(i) { return i.id; }
    });

    function fn1() { }

    source.filter("id", ">", 1);
    assert.deepEqual(source.filter(), ["id", ">", 1]);

    source.filter(fn1);
    assert.strictEqual(source.filter(), fn1);

    source.filter(["id", "<", 2]);
    assert.deepEqual(source.filter(), ["id", "<", 2]);

    source.load().done(function(r) {
        assert.deepEqual(r, [1]);
    });
});

QUnit.test("group", function(assert) {
    var data = [
        { g: 1 },
        { g: 2 },
        { g: 1 }
    ];

    var source = new DataSource({
        store: data
    });

    source.group("a");
    assert.equal(source.group(), "a");

    source.group("a", "b");
    assert.deepEqual(source.group(), ["a", "b"]);

    source.group("g");
    source.load().done(function(r) {
        assert.equal(r.length, 2);
        assert.equal(r[0].key, 1);
    });
});

QUnit.test("select", function(assert) {
    var data = [
        { a: 1, b: 2 }
    ];

    var source = new DataSource({
        store: data
    });

    function fn1() { }

    source.select("a");
    assert.equal(source.select(), "a");

    source.select("a", "b");
    assert.deepEqual(source.select(), ["a", "b"]);

    source.select(fn1);
    assert.strictEqual(source.select(), fn1);

    source.select("b");
    source.load().done(function(r) {
        assert.equal(r[0].b, 2);
        assert.ok(!("a" in r[0]));
    });
});


QUnit.module("requireTotalCount");
QUnit.test("requireTotalCount: fail", function(assert) {
    var done = assert.async();

    $.mockjax({
        url: "odata.org",
        responseTime: 0,
        responseText: {
            d: {
                results: [],
                __count: undefined
            }
        }
    });

    var source = new DataSource({
        store: new ODataStore({
            url: "odata.org"
        }),

        requireTotalCount: true
    });

    source.load()
        .done(function() {
            assert.ok(false);
        })
        .fail(function(error) {
            assert.ok(error);
        })
        .always(function() {
            $.mockjax.clear();
        })
        .always(done);
});

QUnit.test("requireTotalCount: success", function(assert) {
    var data = [
        { a: 1, b: 2 },
        { a: 3, b: 4 }
    ];

    var source = new DataSource({
        store: data
    });

    assert.ok(!source.requireTotalCount(), 'default requireTotalCount value');

    source.requireTotalCount(true);

    assert.ok(source.requireTotalCount(), 'requireTotalCount after change option value');

    source.load().done(function(r, extra) {
        assert.ok(extra);
        assert.equal(extra.totalCount, 2);
        assert.equal(source.totalCount(), 2);
    });
});

//T401687
QUnit.test("totalCount when CustomStore with Promise/A", function(assert) {
    var loadCallback,
        source = new DataSource({
            requireTotalCount: true,
            load: function(e) {
                return {
                    then: function(callback) {
                        loadCallback = callback;
                    }
                };
            }
        });


    source.load().done(function(data, extra) {
        assert.deepEqual(data, [1, 2, 3]);
        assert.ok(extra);
        assert.ok(extra.totalCount, 10);
        assert.deepEqual(source.items(), [1, 2, 3]);
        assert.equal(source.totalCount(), 10);
    });

    loadCallback({
        data: [1, 2, 3],
        totalCount: 10
    });
});

QUnit.module("Custom cases and regressions", moduleConfig);
QUnit.test("expand option for OData store", function(assert) {
    var done = assert.async();

    $.mockjax({
        url: "odata.org",
        responseTime: 0,
        response: function(bag) {
            this.responseText = { value: [bag] };
        }
    });

    new DataSource({
        store: new ODataStore({
            url: "odata.org"
        }),

        expand: "TestExpand"
    }).load()
        .done(function(data) {
            assert.equal(data[0].data.$expand, "TestExpand");
        })
        .always(function() {
            $.mockjax.clear();
        })
        .always(done);
});

QUnit.test("Q456193", function(assert) {
    var store = new ArrayStore(["a", "b", "c"]),
        source = new DataSource(store);

    source.load().done(function(r) {
        assert.deepEqual(r, ["a", "b", "c"]);
        assert.ok(!r[0].__key__);
    });
});

QUnit.test("paging with continuation token", function(assert) {
    function simulateServerResponse(token) {
        if(!token) {
            return {
                data: [1, 2],
                nextToken: 3
            };
        }

        if(token === 3) {
            return {
                data: [3, 4, 5],
                nextToken: 6
            };
        }

        if(token === 6) {
            return {
                data: [6]
            };
        }

        throw Error();
    }


    var source = new DataSource({
        pageSize: null, // means page size varies

        load: function(options) {
            var userData = options.userData,
                token,
                serverResponse;

            if(userData.lastPageIndex && source.pageIndex() > userData.lastPageIndex) {
                return $.Deferred().resolve([]);
            }

            userData.pageTokens = userData.pageTokens || {};

            token = userData.pageTokens[source.pageIndex()];

            serverResponse = simulateServerResponse(token);
            if("nextToken" in serverResponse) {
                userData.pageTokens[1 + source.pageIndex()] = serverResponse.nextToken;
            } else {
                userData.lastPageIndex = source.pageIndex();
            }
            return $.Deferred().resolve(serverResponse.data);
        }
    });

    source.load().done(function(r) {
        assert.deepEqual(r, [1, 2]);
        assert.ok(!source.isLastPage());

        source.load().done(function(r) {
            assert.deepEqual(r, [1, 2]); // page index was not changed, so same data must be returned
            assert.ok(!source.isLastPage());

            source.pageIndex(1 + source.pageIndex());
            source.load().done(function(r) {
                assert.deepEqual(r, [3, 4, 5]);
                assert.ok(!source.isLastPage());

                source.pageIndex(1 + source.pageIndex());
                source.load().done(function(r) {
                    assert.deepEqual(r, [6]);
                    assert.ok(!source.isLastPage()); // still cannot know whether is last page

                    source.pageIndex(1 + source.pageIndex());
                    source.load().done(function(r) {
                        assert.deepEqual(r, []);
                        assert.ok(source.isLastPage());
                    });
                });

            });
        });

    });
});

QUnit.test("B239452", function(assert) {
    var BENCH = {
        title: "Bench"
    };

    var exercises = [{
        equipment: BENCH
    }];

    var ds = new DataSource({
        store: new ArrayStore(exercises),
        filter: ["equipment", BENCH]
    });

    ds.load().done(function(r) {
        assert.equal(r.length, 1);
    });
});

QUnit.test("B251658", function(assert) {
    var ds = new DataSource({
        store: [1, 2, 3, 4, 5, 6, 7, 8, 9],
        pageSize: 5
    });
    ds.pageIndex(1);
    ds.filter("this", "<", 5);
    assert.equal(ds.pageIndex(), 0);

    ds.pageIndex(1);
    ds.filter();
    assert.equal(ds.pageIndex(), 1);
});

QUnit.test("B253895 - Data - DataSource.pageIndex is not set to 0 when search API is applied", function(assert) {
    var ds = new DataSource({
        store: [1, 2, 3, 4, 5, 6, 7, 8, 9],
        pageSize: 5
    });
    ds.pageIndex(1);
    ds.searchExpr("this");
    assert.equal(ds.pageIndex(), 0);

    ds.pageIndex(1);
    ds.searchOperation("=");
    assert.equal(ds.pageIndex(), 0);

    ds.pageIndex(1);
    ds.searchValue("4");
    assert.equal(ds.pageIndex(), 0);

    ds.pageIndex(1);
    ds.searchExpr();
    ds.searchValue();
    ds.searchOperation();
    assert.equal(ds.pageIndex(), 1);
});

QUnit.test("paging should be disabled if ctor options is url (see T314464)", function(assert) {
    assert.ok(!new DataSource("path/to/file/with/json/content.txt").paginate());
});

QUnit.test("StoreLoadOptionAccessors: null and undefined (based on T304670)", function(assert) {
    var ds = new DataSource("");

    // setter
    assert.equal(ds.pageSize(0), undefined);
    // getter
    assert.equal(ds.pageSize(undefined), 0);
    assert.equal(ds.pageSize(null), 0);

    // setter
    assert.equal(ds.sort(null), undefined);
    assert.equal(ds.sort(undefined), undefined);
    // getter
    assert.equal(ds.sort(), undefined);

    // setter
    assert.equal(ds.pageIndex(0), undefined);
    // getter
    assert.equal(ds.pageIndex(undefined), 0);
    assert.equal(ds.pageIndex(null), 0);

    // setter
    assert.equal(ds.group(null), undefined);
    assert.equal(ds.group(undefined), undefined);
    // getter
    assert.equal(ds.group(), undefined);

    // setter
    assert.equal(ds.select(null), undefined);
    assert.equal(ds.select(undefined), undefined);
    // getter
    assert.equal(ds.select(), undefined);

    // setter
    assert.equal(ds.filter(null), undefined);
    assert.equal(ds.filter(undefined), undefined);
    // getter
    assert.equal(ds.filter(), undefined);

    // setter
    assert.equal(ds.paginate(false), undefined);
    // getter
    assert.equal(ds.pageIndex(undefined), false);
    assert.equal(ds.pageIndex(null), false);

    // setter
    assert.equal(ds.searchExpr("foo"), undefined);
    assert.equal(ds.searchExpr(undefined), undefined);
    assert.equal(ds.searchExpr(null), undefined);
    // getter
    assert.equal(ds.searchExpr(), null);

    // setter
    assert.equal(ds.searchValue("foo"), undefined);
    assert.equal(ds.searchValue(undefined), undefined);
    assert.equal(ds.searchValue(null), undefined);
    // getter
    assert.equal(ds.searchValue(), null);

    // setter
    assert.equal(ds.searchOperation("="), undefined);
    // getter
    assert.equal(ds.searchOperation(undefined), "=");
    assert.equal(ds.searchOperation(null), "=");
    assert.equal(ds.searchOperation(), "=");

    // setter
    assert.equal(ds.requireTotalCount(false), undefined);
    // getter
    assert.equal(ds.requireTotalCount(undefined), false);
    assert.equal(ds.requireTotalCount(null), false);
    assert.equal(ds.requireTotalCount(), false);
});
